package com.pig4cloud.pigx.common.customize.api.interceptor;

import cn.hutool.core.codec.Base16Codec;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.MapUtil;
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.common.core.util.SpringContextHolder;
import com.pig4cloud.pigx.common.customize.api.annotation.OpenApi;
import com.pig4cloud.pigx.common.customize.api.constant.OpenApiConstant;
import com.pig4cloud.pigx.common.customize.api.enums.AbnormalEnum;
import com.pig4cloud.pigx.common.customize.api.exception.OpenApiException;
import com.pig4cloud.pigx.common.customize.api.util.OpenApiUtil;
import com.pig4cloud.pigx.common.customize.contants.RegexConstants;
import com.pig4cloud.pigx.common.customize.i18n.util.MessageUtils;
import com.pig4cloud.pigx.common.customize.redis.constants.RedisKeyEnum;
import com.pig4cloud.pigx.common.customize.redis.utils.RedisUtils;
import com.pig4cloud.pigx.common.customize.utils.CommonUtil;
import com.pig4cloud.pigx.user.api.feign.RippleRemoteUserService;
import com.pig4cloud.pigx.user.constant.I18nMessages;
import com.pig4cloud.pigx.user.enums.UserApiKeyEnum;
import com.pig4cloud.pigx.user.vo.UserApiConfigVO;
import com.pig4cloud.pigx.user.vo.UserApiKeyCacheVO;
import lombok.Data;
import org.apache.logging.log4j.util.Strings;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.web.method.HandlerMethod;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;

/**
 * OpenApi校验器
 *
 * @since 2022/11/12 16:42
 */
@Data
public class OpenApiValidator {

    /**
     * 注解配置
     */
    private final OpenApi openApi;
    /**
     * 访问密钥
     */
    private final String accessKey;
    /**
     * 客户端Ip
     */
    private final String clientIp;
    /**
     * 请求路径
     */
    private final String requestPath;
    /**
     * 请求包
     */
    private final HttpServletRequest request;
    /**
     * 响应包
     */
    private final HttpServletResponse response;
    /**
     * apiKey信息
     */
    private UserApiKeyCacheVO apiKey;
    /**
     * Api相关设置
     */
    private UserApiConfigVO apiConfig;
    /**
     * 服务器当前时间（毫秒）
     */
    private Long serverTime;

    private final RedisTemplate redis;
    private final RedisUtils redisUtils;
    private final OpenApiUtil openApiUtil;
    private final CommonUtil commonUtil;
    private final RippleRemoteUserService rippleRemoteUserService;

    /**
     * 构造器
     *
     * @param openApi  注解配置
     * @param request  请求包
     * @param response 相应体
     * @since 2022/11/12 17:07
     */
    public OpenApiValidator(OpenApi openApi, HttpServletRequest request, HttpServletResponse response) {
        this.openApi = openApi;
        this.request = request;
        this.response = response;

        this.redis = SpringContextHolder.getBean(RedisTemplate.class);
        this.redisUtils = SpringContextHolder.getBean(RedisUtils.class);
        this.openApiUtil = SpringContextHolder.getBean(OpenApiUtil.class);
        this.commonUtil = SpringContextHolder.getBean(CommonUtil.class);
        this.rippleRemoteUserService = SpringContextHolder.getBean(RippleRemoteUserService.class);

        this.accessKey = OpenApiUtil.getStringValueFromHeader(request, SecurityConstants.X_API_KEY);
        String clientIp = OpenApiUtil.getStringValueFromHeader(request, SecurityConstants.X_CLIENT_IP);
        this.clientIp = OpenApiUtil.getClientIp(clientIp);
        this.requestPath = request.getMethod() + "_" + request.getRequestURI();
        this.serverTime = System.currentTimeMillis();

        // System.err.println("监测到开放API请求：" + requestPath);
    }

    /**
     * 构建器
     *
     * @param openApi  注解配置
     * @param request  请求包
     * @param response 相应体
     * @since 2022/11/12 17:07
     */
    public static OpenApiValidator build(OpenApi openApi, HttpServletRequest request, HttpServletResponse response) {
        return new OpenApiValidator(openApi, request, response);
    }

    /**
     * 从控制器方法/控制器上获取 {@link OpenApi} 注解
     *
     * @param method 控制器方法
     * @return {@link OpenApi}
     * @since 2022/11/11 15:34
     */
    public static OpenApi getOpenApi(HandlerMethod method) {
        // 优先获取方法上的注解
        if (method.hasMethodAnnotation(OpenApi.class)) {
            return method.getMethodAnnotation(OpenApi.class);
        }

        // 获取类上的注解
        Class<?> controller = method.getBean().getClass();
        if (controller.isAnnotationPresent(OpenApi.class)) {
            return controller.getAnnotation(OpenApi.class);
        }

        return null;
    }

    /**
     * 处理访问频率
     *
     * @return {@link OpenApiValidator}
     * @since 2022/11/11 15:59
     */
    public OpenApiValidator handleRateLimit() {
        // 访问频率、访问频率限制时间范围无限制
        if (this.openApi.rateLimit() <= 0 || openApi.rateLimitRange() <= 0) {
            return this;
        }

        String key = OpenApiUtil.getRateLimitRedisKey(accessKey, clientIp, requestPath);
        Long count = redis.opsForValue().increment(key, 1);
        if (Objects.isNull(count)) {
            // 服务器内部错误
            OpenApiUtil.throwError(AbnormalEnum.MSG.DISCONNECTED);
        }

        // 如果一定时间内第一次请求，根据注解中配置的时间范围和单位设置过期时间
        redisUtils.setExpireIfNotSet(key, openApi.rateLimitRange(), openApi.rateLimitRangeUnit());
        // 如果超过了访问频率限制
        if (count > openApi.rateLimit()) {
            OpenApiUtil.throwError(AbnormalEnum.MSG.TOO_MANY_REQUESTS);
            // System.err.println("超过了访问频率限制，封禁：" + accessKey + "_" + clientIp);

            // 判断累计超过次数 todo

            // 如果超过上限，封禁
        }

        return this;
    }

    /**
     * 处理权限
     *
     * @return {@link OpenApiValidator}
     * @since 2022/11/11 17:26
     */
    public OpenApiValidator handlePermission() {

        UserApiKeyEnum.Permission permission = openApi.permission();
        // 是无权限
        if (Objects.equals(UserApiKeyEnum.Permission.NONE, permission)) {
            return this;
        }

        // 获取ApiKey的权限
        String apiKeyPermission = getApiKey().getPermission();
        if (Strings.isBlank(apiKeyPermission)) {
            // 权限不足
            OpenApiUtil.throwError(AbnormalEnum.MSG.UNAUTHORIZED);
        }

        // 权限错误
        String[] split = apiKeyPermission.split(" ");
        boolean hasPermission = Arrays.stream(split)
                .anyMatch(i -> Objects.equals(permission.getValue(), i));
        if (!hasPermission) {
            OpenApiUtil.throwError(AbnormalEnum.MSG.UNAUTHORIZED);
        }

        return this;

    }

    /**
     * 验证签名
     *
     * @return {@link OpenApiValidator}
     * @since 2022/11/11 17:37
     */
    public OpenApiValidator handleSignature() {

        if (!openApi.needSignature()) {
            return this;
        }

        // 将所有参数按照参数名升序排序
        TreeMap<String, Object> treeMap = new TreeMap<>(Comparator.naturalOrder());
        // 获取queryString中的参数
        Map<String, String[]> queryMap = request.getParameterMap();
        // 获取签名并从参数集合中移除
        String signature = getSignature(queryMap);
        treeMap.putAll(queryMap);

        try {
            // 如果是POST请求，将请求体中的参数也加入到排序
            if (HttpMethod.POST.matches(request.getMethod())) {
                BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()));
                String read = IoUtil.read(reader, false);

                Map bodyMap = commonUtil.parseJson(read, HashMap.class);
                treeMap.putAll(bodyMap);
            }

            // 不可能没有参数，存在诸如tiemstamp、nonce等必传参数
            if (MapUtil.isEmpty(queryMap)) {
                OpenApiUtil.throwError(AbnormalEnum.MSG.INVALID_SIGNATURE);
            }

            // 转换为json
            String json = commonUtil.toJson(treeMap);

            // 加密
            String jsonSignature = signature(getApiKey().getSecretKey(), json);
            // 比较传入的签名和密文
            if (!signature.equalsIgnoreCase(jsonSignature)) {
                // 签名错误
                OpenApiUtil.throwError(AbnormalEnum.MSG.INVALID_SIGNATURE);
            }

        } catch (OpenApiException e) {
            throw e;
        } catch (Exception e) {
            OpenApiUtil.throwError(AbnormalEnum.MSG.DISCONNECTED);
        }

        return this;

    }

    /**
     * 签名
     *
     * @param secretKey 加密密钥
     * @param json      参数json
     * @return {@link String}
     * @since 2022/11/11 18:10
     */
    public static String signature(String secretKey, String json) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException {

        Mac sha256_HMAC = Mac.getInstance(OpenApiConstant.HMAC_SHA256);
        SecretKeySpec spec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), OpenApiConstant.HMAC_SHA256);
        sha256_HMAC.init(spec);

        byte[] hash = sha256_HMAC.doFinal(json.getBytes(StandardCharsets.UTF_8));
        return new String(Base16Codec.CODEC_LOWER.encode(hash));

    }

    /**
     * 从请求参数中获取签名并移除
     *
     * @param map 请求参数
     * @return {@link String}
     * @since 2022/11/11 18:10
     */
    public static String getSignature(Map<String, String[]> map) {
        String value = CommonUtil.getParamString(map, OpenApiConstant.SIGNATURE, true);
        if (Strings.isBlank(value)) {
            // 签名无效
            OpenApiUtil.throwError(AbnormalEnum.MSG.INVALID_SIGNATURE);
        }

        return value;

    }

    /**
     * 处理服务器时间
     *
     * @return {@link OpenApiValidator}
     * @since 2022/11/12 17:23
     */
    public OpenApiValidator handleServerTime() {
        // 请求时间戳
        long timestamp = getTimestamp(request.getParameterMap());
        // 网络抖动最大延迟（毫秒）
        Long maxDelay = getRecvWindow(request.getParameterMap());
        if (Objects.isNull(maxDelay)) {
            // 如果请求没有指定recvWindow，采用Api配置中的默认最大延迟
            maxDelay = getApiConfig().getMaxNetDelay();
        }
        // 网络抖动最大提前（毫秒）
        long maxAdvance = getApiConfig().getMaxNetAdvance();

        if (timestamp - serverTime >= maxAdvance) {
            // 请求时间戳提前过多
            OpenApiUtil.throwError(AbnormalEnum.MSG.INVALID_TIMESTAMP);
        } else if (serverTime - timestamp >= maxDelay) {
            // 请求时间戳延迟过高
            OpenApiUtil.throwError(AbnormalEnum.MSG.TIMEOUT);
        }

        return this;
    }

    /**
     * 从请求参数中获取请求时间戳
     *
     * @param map 请求参数
     * @return {@link long}
     * @since 2022/11/11 18:10
     */
    public static long getTimestamp(Map<String, String[]> map) {
        String value = CommonUtil.getParamString(map, OpenApiConstant.TIMESTAMP, false);
        if (Strings.isBlank(value) || !RegexConstants.POSITIVE_INTEGER_GT0.matcher(value).matches()) {
            // 参数错误
            OpenApiUtil.throwError(AbnormalEnum.MSG.MANDATORY_PARAM_EMPTY_OR_MALFORMED);
        }

        return Long.parseLong(value);
    }

    /**
     * 从请求参数中获取窗口时间
     *
     * @param map 请求参数
     * @return {@link String}
     * @since 2022/11/11 18:10
     */
    public static Long getRecvWindow(Map<String, String[]> map) {
        String value = CommonUtil.getParamString(map, OpenApiConstant.RECV_WINDOW, false);
        if (Strings.isBlank(value)) {
            return null;
        } else if (!RegexConstants.POSITIVE_INTEGER_GT0.matcher(value).matches()) {
            // 参数错误
            OpenApiUtil.throwError(AbnormalEnum.MSG.MANDATORY_PARAM_EMPTY_OR_MALFORMED);
        }

        return Long.parseLong(value);
    }

    /**
     * 获取ApiKey
     *
     * @return {@link UserApiKeyCacheVO}
     * @since 2022/11/12 17:03
     */
    public UserApiKeyCacheVO getApiKey() {
        if (!Objects.isNull(apiKey)) {
            return apiKey;
        }

        // 如果为空，查询
        R<UserApiKeyCacheVO> apiKeyR = rippleRemoteUserService.getApiKeyCache(accessKey, SecurityConstants.FROM_IN, null);
        if (!apiKeyR.getIsSuccess() || Objects.isNull(apiKeyR.getData())) {
            // 服务器内部错误
            OpenApiUtil.throwError(AbnormalEnum.MSG.DISCONNECTED);
        }

        return this.apiKey = apiKeyR.getData();
    }

    /**
     * 获取ApiConfig
     *
     * @return {@link UserApiConfigVO}
     * @since 2022/11/12 17:03
     */
    public UserApiConfigVO getApiConfig() {
        if (!Objects.isNull(this.apiConfig)) {
            return apiConfig;
        }

        Object obj = redis.opsForHash().get(RedisKeyEnum.ConfigHashKey.getKey(), RedisKeyEnum.UserApi.getKey());

        if (Objects.isNull(obj) || !(obj instanceof UserApiConfigVO)) {
            throw new RuntimeException(MessageUtils.get(I18nMessages.SERVICE_ABNORMAL));
        }

        return this.apiConfig = (UserApiConfigVO) obj;
    }
}
